/***************************************************************************
 *   Copyright (c) 2020, China Mobile Communications Group Co.,Ltd.        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <helper/time_support.h>
#include <jtag/jtag.h>
#include "target/target.h"
#include "target/target_type.h"
#include "rtos.h"
#include "helper/log.h"
#include "helper/types.h"
#include "rtos_standard_stackings.h"
#include "target/armv7m.h"
#include "target/cortex_m.h"
#include "rtos_oneos_stackings.h"

struct list_node_type {
	uint32_t next;
	uint32_t prev;
};

struct object_info_type {
	uint32_t type;
	struct list_node_type node;
};

struct OneOS_params {
	const char *target_name;
	unsigned char list_node_offset;
	const unsigned char list_node_width;
	const unsigned char object_info_width;
	const unsigned char pointer_width;
	const unsigned char stack_pointer_offset;
	const unsigned char thread_state_offset;
	const struct rtos_register_stacking *stacking_info_cm3;
	const struct rtos_register_stacking *stacking_info_cm4f;
	const struct rtos_register_stacking *stacking_info_cm4f_fpu;
};

static struct OneOS_params OneOS_params_list[] = {
	{
	"cortex_m",				/* target_name */
	0,						/* list_node_offset; */
	8,						/* list_node_width; */
	12,						/* object_info_width; */
	4,						/* pointer_width; */
	16,						/* stack_pointer_offset */
	40,						/* thread_state_offset */
	&rtos_standard_Cortex_M3_stacking,	/* stacking_info */
	&rtos_OneOS_Cortex_M4F_stacking,
	&rtos_OneOS_Cortex_M4F_FPU_stacking,
	},
	{
	"hla_target",			/* target_name */
	0,						/* list_node_offset; */
	8,						/* list_node_width; */
	12,						/* object_info_width; */
	4,						/* pointer_width; */
	16,						/* stack_pointer_offset */
	40,						/* thread_state_offset */
	&rtos_standard_Cortex_M3_stacking,	/* stacking_info */
	&rtos_OneOS_Cortex_M4F_stacking,
	&rtos_OneOS_Cortex_M4F_FPU_stacking,
	}
};

#define OneOS_NUM_PARAMS ((int)(sizeof(OneOS_params_list)/sizeof(struct OneOS_params)))

static int OneOS_detect_rtos(struct target *target);
static int OneOS_create(struct target *target);
static int OneOS_update_threads(struct rtos *rtos);
static int OneOS_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, char **hex_reg_list);
static int OneOS_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[]);

#define OS_TASK_INIT                  0x00                /* Initialized status. */
#define OS_TASK_READY                 0x01                /* Ready status. */
#define OS_TASK_SUSPEND               0x02                /* Suspend status. */
#define OS_TASK_RUNNING               0x03                /* Running status. */
#define OS_TASK_BLOCK                 OS_TASK_SUSPEND     /* Blocked status. */
#define OS_TASK_CLOSE                 0x04                /* Closed status. */
#define OS_TASK_STAT_MASK             0x0f

struct OneOS_thread_state {
	int value;
	const char *desc;
};

static const struct OneOS_thread_state OneOS_thread_states[] = {
	{ OS_TASK_INIT,  "init" },
	{ OS_TASK_READY,  "ready" },
	{ OS_TASK_SUSPEND,  "suspend" },
	{ OS_TASK_RUNNING,  "running" },
	{ OS_TASK_BLOCK,  "block" },
	{ OS_TASK_CLOSE,  "close" },
};

#define OneOS_NUM_STATES ((int)(sizeof(OneOS_thread_states)/sizeof(struct OneOS_thread_state)))

struct rtos_type OneOS_rtos = {
	.name = "oneos",

	.detect_rtos = OneOS_detect_rtos,
	.create = OneOS_create,
	.update_threads = OneOS_update_threads,
	.get_thread_reg_list = OneOS_get_thread_reg_list,
	.get_symbol_list_to_lookup = OneOS_get_symbol_list_to_lookup,
};

enum OneOS_symbol_values {
	OneOS_VAL_Current_Task = 0,
	OneOS_VAL_Object_Container = 1,
};

static const char * const OneOS_symbol_list[] = {
	"gs_os_current_task",
	"gs_os_object_container",
	NULL
};

static int OneOS_update_threads(struct rtos *rtos)
{
	int retval;
	int tasks_found = 0;
	struct OneOS_params *param;
	uint32_t current_task_addr = 0;

	if (rtos->rtos_specific_params == NULL)
		return -1;

	param = (struct OneOS_params *) rtos->rtos_specific_params;

	if (rtos->symbols == NULL) {
		LOG_ERROR("No symbols for OneOS");
		return -3;
	}

	retval = target_read_buffer(rtos->target,
			rtos->symbols[OneOS_VAL_Current_Task].address,
			param->pointer_width,
			(uint8_t *)&current_task_addr);

	if (0 != current_task_addr) {
		char cur_task_name[32];
		retval = target_read_buffer(rtos->target,
				current_task_addr,
				32,
				(uint8_t *)&cur_task_name);

		if (retval != ERROR_OK) {
			LOG_ERROR("Could not read OneOS current thread");
			return retval;
		}

		LOG_DEBUG("current task addr : 0x%0x name : %s", current_task_addr, cur_task_name);
	}

	struct object_info_type task_object_info;

	retval = target_read_buffer(rtos->target,
			rtos->symbols[OneOS_VAL_Object_Container].address,
			param->object_info_width,
			(uint8_t *)&task_object_info);

	struct list_node_type iter_node = task_object_info.node;
	int thread_list_size = 0;
	int nodes_array_size = 10;
	struct list_node_type * task_nodes = malloc(sizeof(struct list_node_type) * nodes_array_size);
	task_nodes[0] = iter_node;

	while (iter_node.next != (rtos->symbols[OneOS_VAL_Object_Container].address + 4)) {

		if (((iter_node.next - current_task_addr) <= 64) 
				&& (iter_node.next > current_task_addr)){
			param->list_node_offset = iter_node.next - current_task_addr;
		}

		retval = target_read_buffer(rtos->target,
					iter_node.next,
					param->list_node_width,
					(uint8_t *)&iter_node);

		if (retval != ERROR_OK) {
			LOG_ERROR("Could not read OneOS task list node");
			return retval;
		}

		thread_list_size++;

		if ((thread_list_size + 1) > nodes_array_size) {
			nodes_array_size += 10;
			task_nodes = realloc(task_nodes, nodes_array_size);
		}

		task_nodes[thread_list_size] = iter_node;
	}

	LOG_DEBUG("task count : %d", thread_list_size);
	LOG_DEBUG("list_node_offset : %d", param->list_node_offset);

	/* wipe out previous thread details if any */
	rtos_free_threadlist(rtos);
	rtos->current_thread = current_task_addr;
	rtos->current_threadid = current_task_addr;

	if ((thread_list_size  == 0) || (rtos->current_thread == 0)) {
		/* Either : No RTOS threads - there is always at least the current execution though */
		/* OR     : No current thread - all threads suspended - show the current execution
		 * of idling */
		char tmp_str[] = "Current Execution";
		thread_list_size = 1;
		rtos->thread_details = malloc(
				sizeof(struct thread_detail) * thread_list_size);
		if (!rtos->thread_details) {
			LOG_ERROR("Error allocating memory for %d threads", thread_list_size);
			return ERROR_FAIL;
		}
		rtos->thread_details->threadid = 1;
		rtos->thread_details->exists = true;
		rtos->thread_details->extra_info_str = NULL;
		rtos->thread_details->thread_name_str = malloc(sizeof(tmp_str));
		strcpy(rtos->thread_details->thread_name_str, tmp_str);

		rtos->current_thread = 1;
		rtos->current_threadid = 1;
		rtos->thread_count = 1;
		return ERROR_OK;
	} else {
		/* create space for new thread details */
		rtos->thread_details = malloc(
				sizeof(struct thread_detail) * thread_list_size);
		if (!rtos->thread_details) {
			LOG_ERROR("Error allocating memory for %d threads", thread_list_size);
			return ERROR_FAIL;
		}
	}

	/* loop over all threads */
	while (tasks_found < thread_list_size) {

		#define ONEOS_THREAD_NAME_STR_SIZE (32)
		char tmp_str[ONEOS_THREAD_NAME_STR_SIZE];
		unsigned int i = 0;

		rtos->thread_details[tasks_found].threadid = task_nodes[tasks_found].next - param->list_node_offset;

		/* read the name pointer */
		retval = target_read_buffer(rtos->target,
				rtos->thread_details[tasks_found].threadid,
				ONEOS_THREAD_NAME_STR_SIZE,
				(uint8_t *)&tmp_str);
		if (retval != ERROR_OK) {
			LOG_ERROR("Could not read OneOS thread name from target");
			return retval;
		}

		tmp_str[ONEOS_THREAD_NAME_STR_SIZE-1] = '\x00';

		if (tmp_str[0] == '\x00')
			strcpy(tmp_str, "No Name");

		rtos->thread_details[tasks_found].thread_name_str =
			malloc(strlen(tmp_str)+1);
		strcpy(rtos->thread_details[tasks_found].thread_name_str, tmp_str);

		/* Read the thread status */
		int32_t thread_status = 0;
		retval = target_read_buffer(rtos->target,
				task_nodes[tasks_found].next + param->thread_state_offset,
				4,
				(uint8_t *)&thread_status);
		if (retval != ERROR_OK) {
			LOG_ERROR("Error reading thread state from OneOS target");
			return retval;
		}

		for (i = 0; (i < OneOS_NUM_STATES) &&
				(OneOS_thread_states[i].value != (thread_status & OS_TASK_STAT_MASK)); i++) {
			/* empty */
		}

		const char *state_desc;
		if  (i < OneOS_NUM_STATES)
			state_desc = OneOS_thread_states[i].desc;
		else
			state_desc = "Unknown state";

		rtos->thread_details[tasks_found].extra_info_str = malloc(strlen(
					state_desc)+8);
		sprintf(rtos->thread_details[tasks_found].extra_info_str, "State: %s", state_desc);

		rtos->thread_details[tasks_found].exists = true;

		tasks_found++;
	}

	rtos->thread_count = tasks_found;

	return ERROR_OK;
}

static int OneOS_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, char **hex_reg_list)
{
	int retval;
	const struct OneOS_params *param;
	int32_t stack_ptr = 0;

	*hex_reg_list = NULL;
	if (rtos == NULL)
		return -1;

	if (thread_id == 0)
		return -2;

	if (rtos->rtos_specific_params == NULL)
		return -1;

	param = (const struct OneOS_params *) rtos->rtos_specific_params;

	/* Read the stack pointer */
	retval = target_read_buffer(rtos->target,
			thread_id + param->list_node_offset + param->stack_pointer_offset,
			param->pointer_width,
			(uint8_t *)&stack_ptr);
	if (retval != ERROR_OK) {
		LOG_ERROR("Error reading stack frame from OneOS thread");
		return retval;
	}
	LOG_INFO("OneOS: Read stack pointer at 0x%" PRIx32 ", value 0x%" PRIx32 "",
							thread_id + param->list_node_offset + param->stack_pointer_offset,
							stack_ptr);

	/* Check for armv7m with *enabled* FPU, i.e. a Cortex-M4F */
	int cm4_fpu_enabled = 0;
	struct armv7m_common *armv7m_target = target_to_armv7m(rtos->target);
	if (is_armv7m(armv7m_target)) {
		if (armv7m_target->fp_feature == FPv4_SP) {
			/* Found ARM v7m target which includes a FPU */
			uint32_t cpacr;

			retval = target_read_u32(rtos->target, FPU_CPACR, &cpacr);
			if (retval != ERROR_OK) {
				LOG_ERROR("Could not read CPACR register to check FPU state");
				return -1;
			}

			/* Check if CP10 and CP11 are set to full access. */
			if (cpacr & 0x00F00000) {
				/* Found target with enabled FPU */
				cm4_fpu_enabled = 1;
			}
		}
	}

	if (cm4_fpu_enabled == 1) {
		/* Read the LR to decide between stacking with or without FPU */
		uint32_t fp_flag = 0;
		retval = target_read_buffer(rtos->target,
				stack_ptr,
				param->pointer_width,
				(uint8_t *)&fp_flag);
		if (retval != ERROR_OK) {
			LOG_OUTPUT("Error reading stack frame from OneOS thread\r\n");
			return retval;
		}
		if (fp_flag == 1) {
			return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f_fpu, stack_ptr, hex_reg_list);
		} else {
			return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f, stack_ptr, hex_reg_list);
		}
	} else {
		return rtos_generic_stack_read(rtos->target, param->stacking_info_cm3, stack_ptr, hex_reg_list);
	}
}

static int OneOS_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[])
{
	unsigned int i;
	*symbol_list = calloc(
			ARRAY_SIZE(OneOS_symbol_list), sizeof(symbol_table_elem_t));

	for (i = 0; i < ARRAY_SIZE(OneOS_symbol_list); i++) {
		(*symbol_list)[i].symbol_name = OneOS_symbol_list[i];
		(*symbol_list)[i].optional = false;
	}

	return 0;
}

static int OneOS_detect_rtos(struct target *target)
{
	if ((target->rtos->symbols != NULL) &&
			(target->rtos->symbols[OneOS_VAL_Object_Container].address != 0)) {
		LOG_ERROR("OneOS_detect_rtos %0x", target->rtos->symbols[OneOS_VAL_Object_Container].address);
		/* looks like OneOS */
		return 1;
	}
	return 0;
}

static int OneOS_create(struct target *target)
{
	int i = 0;
	while ((i < OneOS_NUM_PARAMS) &&
			(0 != strcmp(OneOS_params_list[i].target_name, target->type->name))) {
		i++;
	}
	if (i >= OneOS_NUM_PARAMS) {
		LOG_ERROR("Could not find target in OneOS compatibility list");
		return -1;
	}

	target->rtos->rtos_specific_params = (void *) &OneOS_params_list[i];
	return 0;
}
